; (function () {
  function HappyDevelopment(selector) {
    return new HappyDevelopment.prototype.init(selector);
  };
  HappyDevelopment.prototype = {
    constructor: HappyDevelopment,
    version: '1.1.0',
    init: function (selector) {//总入口函数 要加油啊，宝成！
      this[0] = selector;
      let res = this.judgeDataType(selector);
      switch (res) {
        case 'Number':
          this.dataType = "Number";
          break;
        case 'String':
          this.dataType = "String";
          break;
        case 'Object':
          this.dataType = "Object";
          if (this.isArray()) {
            this.dataType = "FakeArray";
          }
          break;
        case 'Array':
          this.dataType = "Array";
          break;
        case 'Boolean':
          this.dataType = "Boolean";
          break;
        case 'Undefined':
          this.dataType = "Undefined";
          break;
        case 'Null':
          this.dataType = "Null";
          break;
        case 'Function':
          this.dataType = "Function";
          break;
        case 'Window':
          this.dataType = "Window";
          break;
        default:
          this.dataType = "未收录类型";
          break;
      };
      return this;
    }
  };

  /**
   * 添加静态方法总入口函数
   * @param {*} params 
   */
  HappyDevelopment.extend = function (params) {
    for (const key in params) {
      if (params.hasOwnProperty(key)) {
        const element = params[key];
        this[key] = element;
      }
    }
  };

  /**
   * 添加静态方法
   * 判断类型
   * 伪数组转真数组
   * 监听界面加载
   */
  HappyDevelopment.extend({//添加静态方法-判断类型为主
    /**
      * 功能:判断返回值类型
      * @param {*} sele:需要判断的数据
      * 返回值:Number String Object Array Boolean Undefined Null Function Window 
      * 注意点:NaN属于Number类型
      * 调用实例:let res = hd.judgeDataType(123)
      */
    judgeDataType: function (value) {
      return Object.prototype.toString.call(value).slice(8, -1);
    },

    /**
     * 功能:判断是不是window对象
     * @param {*} sele:需要判断的数据
     * 返回值:boolean
     * 调用实例:let res = hd.isWindow(1)
     */
    isWindow: function (sele) {
      return sele === window;
    },

    /**
     * 功能:判断是不是对象
     * @param {*} sele:需要判断的数据
     * 返回值:boolean
     * 调用实例:let res = hd.isObject({})
     */
    isObject: function (sele) {
      return HappyDevelopment.judgeDataType(sele) == "Object";
    },

    /**
     * 功能:判断是不是函数
     * @param {*} sele:需要判断的数据
     * 返回值:boolean
     * 调用实例:let res = hd.isFunction(function () {
     * })
     */
    isFunction: function (sele) {//判断是不是函数
      return HappyDevelopment.judgeDataType(sele) === "Function";
    },

    /**
     * 功能:判断是不是字符串
     * @param {*} str:需要判断的数据
     * 返回值:boolean
     * 调用实例:let res = hd.isString('sd')
     */
    isString: function (str) {//判断是否是字符串
      return HappyDevelopment.judgeDataType(str) === "String";
    },

    /**
     * 功能:判断是不是数组/真/伪
     * @param {*} sele:需要判断的数据
     * 返回值:boolean
     * 调用实例:let res = hd.isArray({ 0: 'name', 1: 'age', 2: '', length: 3 })
     *         let res = hd.isArray([])
     */
    isArray: function (sele) {//判断是不是数组（真伪数组）
      if (typeof sele === "object" && !HappyDevelopment.isWindow(sele) && 'length' in sele) {
        return true;
      }
      return false;
    },

    /**
     * 功能:真/伪数组转成真数组,并且拷贝(浅)的数组
     * @param {*} arr:真/伪数组
     * 返回值:Array
     * 调用实例:let res = hd.toArray(arr)
     */
    toArray: function (arr) {
      return [].slice.call(arr);
    },

    /**
     * 功能:判断dom是否加载完毕
     * @param {*} fn:回调函数
     * 返回值:undefined
     * 调用实例:let res = hd.ready(function () {})
     */
    ready: function (fn) {
      if (document.readyState == 'complete') {//如果页面已经载入完成
        fn();
      } else if (document.addEventListener) {//判断浏览器是否支持addEventListener方法
        document.addEventListener('DOMContentLoaded', function () {
          fn();
        }, false);
      } else {//浏览器不支持 addEventListener方法
        document.attachEvent('onreadystatechange', function () {
          if (document.readyState == 'complete') {
            fn();
          };
        });
      };
    },
  });

  /**
   * 添加静态方法
   * 判断浏览器类型
   * 浏览器全屏
   * 退出全屏
   * 获取监听全屏/退出全屏事件和FullScreen属性
   */
  HappyDevelopment.extend({//添加静态方法-浏览器相关方法
    /**
    * 功能:判断浏览器类型
    * 返回值:字符串
    * 调用实例:let res=hd.getBrowserType();
    */
    getBrowserType: function () {
      let userAgent = navigator.userAgent; //取得浏览器的userAgent字符串

      let isOpera = userAgent.indexOf("Opera") > -1;//判断是不是Opera
      let isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera; //判断是否IE浏览器
      let isEdge = userAgent.indexOf("Edg") > -1; //判断是否IE的Edge浏览器
      let isFF = userAgent.indexOf("Firefox") > -1; //判断是否Firefox浏览器
      let isSafari = userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") == -1; //判断是否Safari浏览器
      let isChrome = userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Safari") > -1; //判断Chrome浏览器
      let isIE11 = userAgent.indexOf("Trident/7.0") > -1 && userAgent.indexOf("rv") > -1 && !isIE && !isOpera && !isEdge && !isFF && !isSafari && !isChrome;//判断是不是ie11

      if (isIE) {//ie 非ie11
        let reIE = new RegExp("MSIE (\\d+\\.\\d+);");
        reIE.test(userAgent);
        let fIEVersion = parseFloat(RegExp["$1"]);
        if (fIEVersion == 7) {
          return "IE7";
        } else if (fIEVersion == 8) {
          return "IE8";
        } else if (fIEVersion == 9) {
          return "IE9";
        } else if (fIEVersion == 10) {
          return "IE10";
        } else {
          return "IE5不包括5以下";
        }
      } else if (isIE11) {//ie11
        return "IE11"
      } else if (isOpera) {
        return "Opera";
      } else if (isEdge) {
        return "Edge";
      } else if (isFF) {
        return "Firefox";
      } else if (isSafari) {
        return "Safari";
      } else if (isChrome) {
        return "Chrome";
      } else {
        return '非主流浏览器，建议更换！';
      }
    },

    /**
     * 功能:浏览器全屏
     * 返回值:undefined
     * 调用实例:let res = hd.toFullScreen();
     * 兼容性:支持[ie11]
     */
    toFullScreen: function () {
      let elem = document.body;
      elem.webkitRequestFullScreen ?
        elem.webkitRequestFullScreen() :
        elem.mozRequestFullScreen ?
          elem.mozRequestFullScreen() :
          elem.msRequestFullscreen ?
            elem.msRequestFullscreen() :
            elem.requestFullScreen ?
              elem.requestFullScreen() :
              alert("浏览器不支持全屏");
    },

    /**
     * 功能:浏览器退出全屏
     * 返回值:undefined
     * 调用实例:let res = hd.exitFullscreen();
     * 兼容性:支持[ie11]
     */
    exitFullscreen: function () {
      let elem = parent.document;
      elem.webkitCancelFullScreen ?
        elem.webkitCancelFullScreen() :
        elem.mozCancelFullScreen ?
          elem.mozCancelFullScreen() :
          elem.cancelFullScreen ?
            elem.cancelFullScreen() :
            elem.msExitFullscreen ?
              elem.msExitFullscreen() :
              elem.exitFullscreen ?
                elem.exitFullscreen() :
                alert("切换失败,可尝试Esc退出");
    },

    /**
     * 功能:返回浏览器的Fullscreenchang事件和FullScreen属性
     * 返回值:{typeFullscreenchange: "webkitfullscreenchange", typeFullScreen: "webkitIsFullScreen"}
     * 调用实例:let res = hd.findCorrespondingBrowserFullscreenchange();
     * 实例:https://juejin.cn/post/6855437179054489614
     * 兼容性:本身不存在兼容性 但是随之未来的监听不支持ie 要想解决ie的问题，就监听浏览器窗口改变吧
     */
    findCorrespondingBrowserFullscreenchange: function () {
      switch (HappyDevelopment.getBrowserType()) {
        case 'Chrome':
          return {
            typeFullscreenchange: 'webkitfullscreenchange',
            typeFullScreen: 'webkitIsFullScreen'
          };
        case 'Edge':
          return {
            typeFullscreenchange: 'webkitfullscreenchange',
            typeFullScreen: 'webkitIsFullScreen'
          };
        case 'Firefox':
          return {
            typeFullscreenchange: 'mozfullscreenchange',
            typeFullScreen: 'mozFullScreen'
          };
        case 'IE7':
          return {
            typeFullscreenchange: 'msfullscreenchange',
            typeFullScreen: 'msFullscreenElement'
          };
        case 'IE8':
          return {
            typeFullscreenchange: 'msfullscreenchange',
            typeFullScreen: 'msFullscreenElement'
          };
        case 'IE9':
          return {
            typeFullscreenchange: 'msfullscreenchange',
            typeFullScreen: 'msFullscreenElement'
          };
        case 'IE10':
          return {
            typeFullscreenchange: 'msfullscreenchange',
            typeFullScreen: 'msFullscreenElement'
          };
        case 'IE11':
          return {
            typeFullscreenchange: 'msfullscreenchange',
            typeFullScreen: 'msFullscreenElement'
          };
        default:
          return {
            typeFullscreenchange: 'fullscreenchange',
            typeFullScreen: 'fullscreen'
          };
      }
    }
  })

  /**
   * 添加静态方法
   * 创建过去几天的数组
   */
  HappyDevelopment.extend({//添加静态方法-时间相关方法
    /**
     * 功能:创建过去几天的数组
     * @param {*} num:决定创建过去几天的数组（默认七天）
     * 返回值:时间数组
     * 调用实例:let res = hd.createDay(1);
     */
    createDay: function (num = 7) {
      return [...Array(num).keys()].map(days => new Date(Date.now() - 86400000 * days));
    },
  })

  /**
   * 添加静态方法
   * 获取范围随机数
   */
  HappyDevelopment.extend({//添加静态方法-数字
    /**
     * 功能:获取随机数:包括最小值不包括最大值==>[最小值,最大值)
     * @param {*} min:最小值
     * @param {*} max:最大值
     * 返回值:最小值不包括最大值==>[最小值,最大值)
     * 调用实例:let res = hd.getRandomNumber(10, 20);
     */
    getRandomNumber: function (min, max) {
      return Math.floor(Math.random() * (max - min)) + min;
    },

  })

  /**
   * 添加静态方法
   * 生成随机编码
   * 字符串两端去空格
   */
  HappyDevelopment.extend({//添加静态方法-字符串
    /**
     * 功能:生成一段随机编码
     * @param {*} strLength:决定随机编码的长度
     * 返回值:字符串
     */
    randomMark: function (strLength = 16) {
      let arr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "g", "h", "j", "k", "l", "z", "x", "c", "v", "b", "n", "m"];
      let str = '';
      for (let i = 0; i < Number(strLength); i++) {
        let random = Math.floor(Math.random() * arr.length);
        str += arr[random];
      };
      return str;
    },

    /**
     * 功能:去除字符串两端的空格
     * @param {*} str:需要字符串两端的空格的字符串
     * 返回值:string
     */
    trim: function (str) {
      if (str.trim()) {
        return str.trim();
      } else {//不支持使用正则
        return str.replace(/^\s+|\s+$/g, '');
      }
    },
  })

  /**
   * 添加静态方法
   * 防抖函数
   * 节流函数
   */
  HappyDevelopment.extend({//添加静态方法-防抖函数-节流函数
    /**
     * 功能:防抖函数
     * 参数:
     *    fn:事件触发的回调函数
     *    interval:事件触发的延迟时间
     *    flag:真 ==> 第一次事件触发的时候立即触发
     *    flag:假 ==> 全部延迟触发事件
     * 返回值:无
     * 
     * 在vue中的调用实例(methods的方法中)
     * inputValue: debounce(
     *    console.log(this.inputValue);
     * },1000,false)
     * 
     * keyup:debounce(function(name){
     *    console.log(name)
     * },500,false),
     */
    debounce: function (fn, interval, flag) { //防抖(flag的作用true==》第一次的时候立即触发 false==》全部延迟触发)
      let timer = null;
      console.log('996');
      return function (...args) {
        clearInterval(timer);
        if (flag) { //上来就触发
          let callNow = !timer //真
          timer = setTimeout(() => { //假
            timer = null;
            fn.call(this);
          }, interval)
          if (callNow) {
            fn.apply(this, args);
          }
        } else { //全部延时处理
          timer = setTimeout(() => {
            fn.apply(this, args);
          }, interval)
        }
      }
    },

    /**
     * 功能:节流函数
     * 参数:
     *    fn:事件触发的回调函数
     *    interval:事件触发的延迟时间
     * 返回值:无
     * 
     * 调用实例:
     * 在vue中的调用实例(methods的方法中)
     * keyup:throttle(function(name){
     *    console.log(name)
     * },500),
     */
    throttle: function (fn, interval) { //节流
      let perviousTime = 0;
      return function () {
        let nowTime = Date.now(); //现在的时间不停的在更新
        if (nowTime - perviousTime > interval) {
          fn.call(this);
          perviousTime = nowTime;
        }
      }
    },
  })

  /**
   * 添加静态方法
   * 深拷贝/对象/数组
   * 获取唯一标识符所在item，并放到数组中返回
   * 获取唯一标识符所在item及其祖先item的唯一标识符，并放到数组中返回
   */
  HappyDevelopment.extend({//添加静态方法-递归循环相关方法
    /**
     * 业务需求描述：很多时候，后台给我们返回的数据不符合我们的需求，这时候就需要根据已有数据创造一些数据。
     * 功能:功能概述：递归数组，向带有children属性的item中添加自定义属性
     * @param {*} arr:需要添加属性的数组
     * @param {*} addAttrName:添加属性的名称
     * @param {*} sourceAttrName:需要复制的属性名称
     * @param {*} defaultProps:自定义查找属性(默认{children:'children'})
     * 返回值:undefined
     * 调用实例:hd.deepAddAttr(arrData, 'xbc8866', 'value');
     *        console.log(arrData)
     */
    deepAddAttr: function (arr, addAttrName, sourceAttrName, defaultProps = {
      children: 'children'
    }) {
      HappyDevelopment(arr).each(function (item, index) {
        item[addAttrName] = item[sourceAttrName];
        if (item[defaultProps.children] && item[defaultProps.children].length > 0) {//如果item存在children属性，并且children属性不为空
          HappyDevelopment.deepAddAttr(item.children, addAttrName, sourceAttrName, defaultProps)
        }
      });
    },

    /**
    * @param targetObj:源对象/数组
    * @param resultObj:需要拷贝数据的对象/数组
    * 功能:深拷贝对象数组
    * 返回值:undefined
    * 调用实例:let arr = [];
    *         hd.deepCopy(arrData, arr);
    *         console.log(arr);
    * 注意点:在调用的时候，需要保持targetObj和resultObj的类型一致
    * 兼容性:支持[ie8及以下]放心使用
    */
    deepCopy: function (targetObj, resultObj) {
      for (let key in targetObj) {
        if (targetObj.hasOwnProperty(key)) {
          let value = targetObj[key];
          if (HappyDevelopment(value).judgeDataType() === 'Array') {//拷贝对象的item为数组
            resultObj[key] = [];//在空对象中设置key属性的空数组
            HappyDevelopment.deepCopy(value, resultObj[key]);//将数组item和key属性的空数组放入循环，进行下一次循环
          } else if (HappyDevelopment(value).isObject()) {//拷贝对象的item为对象
            resultObj[key] = {};//在空对象中设置key属性的空对象
            HappyDevelopment.deepCopy(value, resultObj[key]);//将对象item和key属性的空对象放入循环，进行下一次循环
          } else {//遍历对象非数组和对象（简单类型）
            resultObj[key] = value;//在空对象设置key属性，并将item作为value赋值到key属性上（其实上面两个if最后都会走这个循环，即使它的item是对象和数组，因为对象和数组，会被逐级拆解掉）
          }
        }
      }
    },

    /**
     * 功能:根据markId找到循环数据中对应的item
     * @param {*} arr:循环数据
     * @param {*} markId:item的唯一值
     * @param {*} tempArr:放找到item的数组
     * @param {*} defaultProps:自定义查找属性
     * 返回值:返回对应markId所在item的数组
     * 调用实例:let res = hd.getTarget(arrData, '1.3.1.1', [], {
     *           mark: 'value',
     *           children: 'children'
     *         });
     */
    getTarget: function (arr, markId, tempArr, defaultProps = {
      mark: 'mark',
      children: 'children'
    }) {
      HappyDevelopment(arr).each(function (item) {
        if (item[defaultProps.mark] === markId) {
          tempArr.push(item);
          return false;//中止循环
        } else {
          if (!!item[defaultProps.children]) {//这个不包扩 children:[]的情况
            HappyDevelopment.getTarget(item[defaultProps.children], markId, tempArr, defaultProps);
          } else {
            return true;//跳过本次循环
          }
        }
      })
      return tempArr;

      // for (let i = 0; i < arr.length; i++) {
      //   if (arr[i].mark == markId) {
      //     tempArr.push(arr[i]);
      //     break;
      //   } else {
      //     if (!!arr[i].children) {
      //       getTarget(arr[i].children, markId, tempArr)
      //     } else {
      //       continue;
      //     }
      //   }
      // };
      // return tempArr;
    },

    /**
     * 注意点:loopArr的和里面的loopArr相对应,这是一个缺点，不适和挂载在这里，还是弄到原型上吧
     * @param {*} arr:循环数据
     * @param {*} markId:item的唯一值
     * @param {*} tempArr:放找到item的数组
     * @param {*} defaultProps:自定义查找属性
     * 调用实例:let res = hd.getTargetAndParents(loopArr, "6244f76be38fe16616adeah975e7hdb7", []);
     * 返回值:返回对应markId所在item的数组及其父item组成的数组
     */
    getTargetAndParents: function (arr, markId, tempArr, defaultProps = {
      mark: 'mark',
      pid: 'pid',
      children: 'children'
    }) {
      HappyDevelopment(arr).each(function (item, index) {
        if (item[defaultProps.mark] == markId) {
          tempArr.unshift(item);
          HappyDevelopment.getTargetAndParents(arrData, item[defaultProps.pid], tempArr, defaultProps);
          return false;
        } else {
          if (!!item[defaultProps.children]) {
            HappyDevelopment.getTargetAndParents(item[defaultProps.children], markId, tempArr, defaultProps);
          } else {
            return true;
          }
        }
      })
      // for (var i = 0; i < arr.length; i++) {
      //   let item = arr[i];
      //   if (item[defaultProps.mark] == markId) {
      //     tempArr.unshift(item);
      //     HappyDevelopment.getTargetAndParents(arrData, item[defaultProps.pid], tempArr, defaultProps);
      //     break;
      //   } else {
      //     if (!!item[defaultProps.children]) {
      //       HappyDevelopment.getTargetAndParents(item[defaultProps.children], markId, tempArr, defaultProps);
      //     } else {
      //       continue;
      //     }
      //   }
      // }
      return tempArr;
    }
  })

  /**
   * 解决文件手动上传的问题
   * 文件下载
   */
  HappyDevelopment.extend({
    /**
     * 功能:解决文件手动上传的问题
     * 参数:
     *    http:发送ajax请求
     *    baseUrl:公共的URL地址
     *    obj:参数集合
     *        url:接口请求地址
     * 返回值:
     *    res:200的返回值
     *    err:200的返回值      
     */
    formDataUpData: function (http, baseUrl, obj) {
      //set增加数据的时候会覆盖，form中是允许相同的name的
      //append增加数据的时候不会覆盖，form中是允许相同的name的
      //delete删除数据的
      //get/getAll获取对用key值的数据
      let formData = new FormData();
      for (let [key, value] of Object.entries(obj)) {
        if (key != 'url') {
          formData.append(key, value);
        }
      }
      let headers = {};
      headers['Content-Type'] = 'multipart/form-data';
      return new Promise((resolve, reject) => {
        http({
          method: 'post',
          url: baseUrl + obj.url,
          data: formData,
          headers: headers
        }).then(res => {
          resolve(res)
        }).catch(err => {
          reject(err)
        })
      })
    },

    /**
     * 功能:文件下载
     * 参数:
     *    fileName:文件名称
     *    URL:下载地址
     *    target:打开方式(当前页面打开:默认/新页面打开)
     * 返回值:无
     * 注意点:download属性只能在同域名下才能生效不然无效
     */
    downloadFile: function (filename, URL, target = '_self') {
      let DownloadLink = document.createElement('a');
      DownloadLink.style = 'display: none';
      DownloadLink.target = target;
      DownloadLink.download = filename;
      DownloadLink.href = URL;
      document.body.appendChild(DownloadLink);
      DownloadLink.click();
      document.body.removeChild(DownloadLink);
    }
  })

  /**
   * 获取图片的base64编码
   * base64转file文件流
   * 获取文件base64编码
   * base64转blob
   */
  HappyDevelopment.extend({
    /**
     * 功能:img元素转base64编码
     * @param {*} img:img元素
     * 返回值:base64编码
     * 调用实例:let img = document.querySelector('.img');
     *          hd.getBase64Image(img)
     * 注意点，这个方法只能用于非http/https路径
     */
    imgDomToBase64: function (img) {
      var canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      var ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0, img.width, img.height);
      var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
      var dataUrl = canvas.toDataURL("images/" + ext);
      return dataUrl;
    },

    /**
     * 功能:img中srx属性转base64
     * 返回值:base64编码
     * 调用示例 let url="http://xxx.jpg"
     * imgDomToBase64(url).then(res=>{
     *  console.log(res)
     * })
     * 注意点，这个方法适用于http/https路径
     * @param {*} url:http/https://xxx.jpg
     */
    urlToBase64: function (url) {
      return new Promise((resolve, reject) => {
        let image = new Image();
        image.onload = function () {
          let canvas = document.createElement('canvas');
          canvas.width = this.naturalWidth;
          canvas.height = this.naturalHeight;
          // 将图片插入画布并开始绘制
          canvas.getContext('2d').drawImage(image, 0, 0);
          // result
          let result = canvas.toDataURL('image/png')
          resolve(result);
        };
        // CORS 策略，会存在跨域问题https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
        image.setAttribute("crossOrigin", 'Anonymous');
        image.src = url;
        // 图片加载失败的错误处理
        image.onerror = () => {
          reject(new Error('urlToBase64 error'));
        };
      })
    },

    /**
     * 功能:base64转file文件流
     * @param {*} base64:base64
     * @param {*} filename:文件流对象中的名称
     * 返回值:文件流对象
     */
    base64ToFile: function (base64, filename = "file") {
      let arr = base64.split(',');
      let mime = arr[0].match(/:(.*?);/)[1];
      let suffix = mime.split('/')[1];// 图片后缀
      let bstr = atob(arr[1]);
      let n = bstr.length;
      let u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], `${filename}.${suffix}`, { type: mime })
    },

    /**
     * 功能:文件流转base64
     * @param {*} file:文件流
     * @param {*} format:允许的文件流后缀名
     * @param {*} size:文件大小
     * @param {*} formatMsg:文件后缀名错误提示 
     * @param {*} sizeMsg:文件大小错误提示
     * 返回值:{base64String:string,suffix:'文件后缀名'}
     */
    fileToBase64String: function (file, format = ['jpg', 'jpeg', 'png', 'gif'], size = 20 * 1024 * 1024, formatMsg = '文件格式不正确', sizeMsg = '文件大小超出限制') {
      return new Promise((resolve, reject) => {
        // 格式过滤
        let suffix = file.type.split('/')[1].toLowerCase();
        let inFormat = false;
        for (let i = 0; i < format.length; i++) {
          if (suffix === format[i]) {
            inFormat = true;
            break;
          }
        }
        if (!inFormat) {
          reject(formatMsg);
        }
        // 大小过滤
        if (file.size > size) {
          reject(sizeMsg);
        }
        // 转base64字符串
        let fileReader = new FileReader();
        fileReader.readAsDataURL(file);
        fileReader.onload = () => {
          let res = fileReader.result;
          resolve({ base64String: res, suffix: suffix });
          reject('异常文件，请重新选择');
        }
      })
    },

    /**
     * 功能:base64转blob
     * @param {*} base64 
     */
    base64ToBlob: function (base64) {
      let arr = base64.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    },

    /**
     * 功能:blob格式转base64
     * @param {*} blob:blob格式的数据
     * @param {*} callback:处理函数
     * 
     * blobToDataURL(blob, function (dataurl) {
     *  console.log(dataurl);
     * });
     */
    blobToDataURL: function (blob, callback) {
      let a = new FileReader();
      a.onload = function (e) { callback(e.target.result); }
      a.readAsDataURL(blob);
    }
  })

  /**
   * 添加实例方法总入口函数
   * @param {*} params 
   */
  HappyDevelopment.prototype.extend = function (params) {
    for (const key in params) {
      if (params.hasOwnProperty(key)) {
        const element = params[key];
        this[key] = element;
      }
    }
  }

  /**
   * 添加实例方法
   * 判断类型的方法
   * 判断是否是'      '方法
   */
  HappyDevelopment.prototype.extend({//添加实例方法-判断类型为主
    /**
      * 功能:获取返回值类型
      * 返回值:Number String Object Array Boolean Undefined Null Function Window 
      * 注意点:NaN属于Number类型
    */
    judgeDataType: function () {
      return Object.prototype.toString.call(this[0]).slice(8, -1);
    },

    isFunction: function () {//判断是不是函数
      return this.judgeDataType() === "Function";
    },

    isWindow: function () {//判断是不是window对象
      return this[0] === window;
    },

    isObject: function () {//判断是不是对象
      return this.judgeDataType() === "Object"
    },

    isString: function (str) {//判断是否是字符串
      return this.judgeDataType() === "String";
    },

    isArray: function () {//判断是不是数组（真伪数组）
      if (typeof this[0] === "object" && !this.isWindow() && 'length' in this[0]) {
        return true;
      }
      return false;
    },

    isEmptyString: function () {//判断是不是'        '字符串
      if (this.isString()) {
        return this.trim(this[0]).length == 0;
      } else {
        throw Error('你传入的不是字符串，无法判断是否为空字符串')
      }
    },
  })

  /**
   * 添加实例方法
   * each方法:遍历方法 item==>值 index==>索引 true==>跳过循环 false==>中止循环 this指向item
   * map方法:修改数组结构方法 
   */
  HappyDevelopment.prototype.extend({//添加实例方法-遍历
    /**
     * 功能:遍历循环数组/对象
     * @param {*} fn:传入的回调函数
     * 返回值:遍历循环的数组/对象
     * 调用实例:let res = hd(arr).each(function (item, index) {})
     *         console.log(res == arr);//true
     */
    each: function (fn) {
      // if (HappyDevelopment(fn).isFunction()) {
      //   if (this.isArray()) {
      //     for (const [index, element] of Object.entries(this[0])) {//有兼容性问题
      //       let res = fn.call(element, element, index);
      //       if (res == true) {
      //         continue;
      //       } else if (res == false) {
      //         break;
      //       }
      //     };
      //     return this[0];
      //   } else if (this.isObject()) {
      //     for (const key in this[0]) {
      //       if (this[0].hasOwnProperty(key)) {
      //         const element = this[0][key];
      //         let res = fn.call(element, element, key)//修改this属性
      //         if (res == true) {//真 跳过本次循环
      //           continue;
      //         } else if (res == false) {//假 中止循环
      //           break;
      //         }
      //       }
      //     }
      //     return this[0]
      //   } else {
      //     throw Error('你传入的不是对象或者数组（真/假数组），无法遍历！')
      //   }
      // } else {
      //   throw Error('你传入each的参数不是一个函数！')
      // }

      if (HappyDevelopment(fn).isFunction()) {
        if (this.isArray()) {
          for (let index = 0; index < this[0].length; index++) {
            const element = this[0][index];
            let res = fn.call(element, element, index);
            if (res == true) {
              continue;
            } else if (res == false) {
              break;
            }
          };
          return this[0];
        } else if (this.isObject()) {
          for (const key in this[0]) {
            if (this[0].hasOwnProperty(key)) {
              const element = this[0][key];
              let res = fn.call(element, element, key)//修改this属性
              if (res == true) {//真 跳过本次循环
                continue;
              } else if (res == false) {//假 中止循环
                break;
              }
            }
          }
          return this[0]
        } else {
          throw Error('你传入的不是对象或者数组（真/假数组），无法遍历！')
        }
      } else {
        throw Error('你传入each的参数不是一个函数！')
      }
    },

    /**
     * 功能:方法返回一个新数组，数组中的元素为原始数组元素调用函数处理后的值
     * @param {*} fn:传入的回调函数
     * 返回值:Array
     * 调用实例:let res = hd(arr).map(function (item, index) {return item.name});console.log(res == arr);//false
     */
    map: function (fn) {
      if (HappyDevelopment(fn).isFunction()) {
        let temArr = [];
        if (this.isArray()) {//判断是否是数组
          for (let index = 0; index < this[0].length; index++) {
            const element = this[0][index];
            let res = fn(element, index);
            if (res !== undefined) {
              temArr.push(res);
            }
          }
          return temArr;
        } else if (this.isObject()) {//判断是否是对象
          for (const key in this[0]) {
            if (this[0].hasOwnProperty(key)) {
              const element = this[0][key];
              let res = fn(element, key);
              if (res !== undefined) {
                temArr.push(res);
              }
            }
          }
          return temArr;
        } else {
          throw Error('你传入的不是对象或者数组（真/假数组），无法遍历！')
        }
      } else {
        throw Error('你传入map的参数不是一个函数！')
      }
    }
  })

  /**
   * 添加实例方法
   * 去除首尾空格
   * 统计字符串中某个字母出现的次数
   * 统计一个字符串中所有个字母出现的次数
   * 字符串序列化
   * 对象字符串化
   */
  HappyDevelopment.prototype.extend({//添加实例方法-字符串
    trim: function () {//去除字符串两端的空格
      if (this.isString()) {
        if (this[0].trim()) {//判断是否支持trim方法
          return this[0].trim();
        } else {//不支持使用正则
          return this[0].replace(/^\s+|\s+$/g, '');
        }
      } else {
        throw Error('你传入的不是字符串，无法去除两端空格')
      }
    },

    /**
     * 功能：统计一个字符串中某个字母出现的次数
     * 调用实例:hd('laskfjlwiejflkasdfl1986532165').findOneStrCount('f');
     * @param {*} oneStr
     * 返回值:number
     */
    findOneStrCount: function (oneStr) {
      if (arguments.length == 1 && HappyDevelopment(oneStr).isString()) {
        if (this.isString()) {
          let count = 0;
          let pos = this[0].indexOf(oneStr);
          while (pos != -1) { //找到了
            count++;
            pos = this[0].indexOf(oneStr, pos + 1);
          };
          return count;
        } else {
          throw Error('你传入的不是字符串，无法统计一个字符串中某个字母出现的次数')
        }
      } else {
        throw Error('findOneStrCount只能输入一个参数，且类型为字符串！')
      }
    },

    /**
     * 功能统计一个字符串中所有个字母出现的次数
     * 返回值:Array
     */
    findAllStrCount: function () {
      if (this.isString()) {
        let str = this[0];
        let newArr = [...new Set(str)].map(item => {
          return {
            [item]: 0
          }
        });
        for (let i = 0; i < str.length; i++) {
          let oneStr = str[i];
          newArr.forEach(item => {
            if (oneStr in item) {
              item[oneStr]++;
            }
          })
        };
        return newArr;
      } else {
        throw Error('你传入的不是字符串，无法统计一个字符串中所有个字母出现的次数')
      }

    },

    /**
     * 功能:字符串序列化
     * 返回值:对象
     */
    strParse: function () {
      if (this.isString()) {
        let str = this[0];
        let json = {};
        str.split("&").forEach(item => {
          let tempArr = item.split('=');
          json[tempArr[0]] = decodeURIComponent(tempArr[1]);
        });
        return json;
      } else {
        throw Error('你传入的不是字符串，无法序列化')
      }

    },

    /**
     * 功能:对象字符串化
     * 返回值:字符串
     */
    objStringify: function () {
      if (this.isObject() && !this.isArray()) {
        let str = '';
        this.each(function (item, index) {
          str += `${index}=${item}&`
        });
        str = str.substring(0, str.length - 1)
        return str;
      } else {
        throw Error('你传入不是对象，无法对象字符串化')
      }

    },

    /**
     * 功能:将一个字符串的首字母大写后返回
     * 输入:xbc
     * 输出:Xbc
     * 是否修改原字符串:否
     * @param {*} str:需要处理的字符串
     * 返回值:字符串
     */
    cached: function () { //将一个字符串的首字母大写后返回
      if (this.isString()) {
        return this[0].charAt(0).toUpperCase() + this[0].slice(1)
      } else {
        throw Error('本函数要求输入实例中输入的是一个字符串,而你输入的不是！')
      }
    },

    /**
     * 功能:将使用连字符-连接的字符串转化成驼峰标识的字符串
     * 是否修改原字符串:否
     * 输入值:'x-asfd-asdf'
     * 返回值:'xAsfdAsdf'
     */
    camelize: function () {
      if (this.isString()) {
        let reg = /-(\w)/g;
        return this[0].replace(reg, function (_, c) {
          return c ? c.toUpperCase() : '';
        })
      } else {
        throw Error('本函数要求输入实例中输入的是一个字符串,而你输入的不是！')
      }
    }
  })

  /**
   * 添加实例方法-深拷贝
   * 深拷贝/对象/数组
   * 获取唯一标识符所在item，并放到数组中返回
   * 获取唯一标识符所在item及其祖先item的唯一标识符，并放到数组中返回
   */
  HappyDevelopment.prototype.extend({//添加实例方法-递归循环
    /**
     * 调用实例：hd('需要深拷贝的对象/数组').deepCopy()
     * 功能：深拷贝对象/数组
     * 返回值:拷贝好的对象/数组
     * 调用实例:let res = hd(obj).deepCopy();console.log(res === obj);//false
     */
    deepCopy: function () {//深拷贝
      if (this.dataType === "FakeArray" || this.dataType === "Object") {//对象/伪数组
        let obj = {};
        HappyDevelopment.deepCopy(this[0], obj);
        return obj;
      } else if (this.dataType === "Array") {//真数组
        let arr = [];
        HappyDevelopment.deepCopy(this[0], arr);
        return arr;
      }
    },

    /**
     * 业务需求描述：很多时候，后台给我们返回的数据不符合我们的需求，这时候就需要根据已有数据创造一些数据。
     * 功能概述：递归数组，向带有children属性的item中添加自定义属性
     * @param {*} addAttrName:addAttrName：添加属性的名称
     * @param {*} sourceAttrName:sourceAttrName：需要复制的属性名称
     * @param {*} bool:是否修改原数组
     * @param {*} defaultProps:自定义查找属性
     * 返回值:Array
     * 调用实例:let res = hd(arrData).deepAddAttr('title886', 'label');console.log(res === arrData);//true
     *        let res = hd(arrData).deepAddAttr('title886', 'label', true);console.log(res === arrData);//false
     */
    deepAddAttr: function (addAttrName, sourceAttrName, bool = false, defaultProps = {
      children: 'children'
    }) {
      if (bool) {
        let res = this.deepCopy();
        HappyDevelopment.deepAddAttr(res, addAttrName, sourceAttrName, defaultProps);
        return res;
      } else {
        HappyDevelopment.deepAddAttr(this[0], addAttrName, sourceAttrName, defaultProps);
        return this[0];
      }
    },

    /**
     * @param {*} markId:item的唯一值
     * @param {*} defaultProps:自定义查找属性
     * 返回值:返回对应markId所在item的数组
     * 调用实例:hd(arrData).getTarget("1.3.3", {
     *  mark: 'value',
     *  children: 'children'
     * });
     */
    getTarget: function (markId, defaultProps = {
      mark: 'mark',
      children: 'children'
    }) {
      return HappyDevelopment.getTarget(this[0], markId, [], defaultProps);
    },

    /**
     * 注意点:私有方法，为getTargetAndParents服务，不做调用
     * @param {*} arr 
     * @param {*} markId 
     * @param {*} tempArr 
     * @param {*} defaultProps 
     */
    _getTargetAndParents: function (arr, markId, tempArr, defaultProps = {
      mark: 'mark',
      pid: 'pid',
      children: 'children'
    }) {
      for (var i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (item[defaultProps.mark] == markId) {
          tempArr.unshift(item);
          this._getTargetAndParents(this[0], item[defaultProps.pid], tempArr, defaultProps);
          break;
        } else {
          if (!!item[defaultProps.children]) {
            this._getTargetAndParents(item[defaultProps.children], markId, tempArr, defaultProps);
          } else {
            continue;
          }
        }
      }
      return tempArr;
    },

    /**
    * @param {*} markId:item的唯一值
    * @param {*} defaultProps:自定义查找属性
    * 返回值:返回对应markId所在item的数组及其父item
    * 调用实例: hd(loopArr).getTargetAndParents("6244f76be38fe16616adeah975e7hdb7");
    */
    getTargetAndParents: function (markId, defaultProps = {
      mark: 'mark',
      pid: 'pid',
      children: 'children'
    }) {
      return this._getTargetAndParents(this[0], markId, [], defaultProps)
    }
  })

  /**
   * 添加实例方法-对象操作
   * 判断是不是一个{}对象
   * 判断对象中是否含有value为空的key
   * 删除对象中value.length==0的key
   * 根据需要删除key值组成的数组和value.length==0删除对象中的key
   */
  HappyDevelopment.prototype.extend({//添加实例方法-对象
    /**
     * 调用实例：hd('需要判断的对象').judgeObjIsEntry()
     * 功能:判断对象是不是一个{}的对象
     * 返回值:boolean
     */
    judgeObjIsEntry() {
      if (this.dataType === "FakeArray" || this.dataType === "Object") {
        let arr = Object.keys(this[0]);
        if (arr.length > 0) { //有值
          return false //非空对象
        } else { //无值
          return true//空对象
        }
      } else {
        throw Error('本函数为判断对象是不是一个{}的函数，请输入对象或者伪数组')
      }
    },

    /**
     * 调用实例：hd('需要判断的对象').judgeObjAttrIsEmpty()
     * 功能:判断对象是否含有value为空的key
     * 返回值:boolean
     */
    judgeObjAttrIsEmpty() {
      if (this.dataType === "FakeArray" || this.dataType === "Object") {
        let flag = false; //默认targetObj对象中不存在空值
        this.each(function (item, index) {
          if (item.length == 0) {
            flag = true;//修改状态
            return false;//中止循环并返回
          };
        });
        return flag;
      } else {
        throw Error('本函数为删除判断对象中是否含有value为空的函数，请输入对象或者伪数组')
      }
    },

    /**
     * 调用实例:hd('需要删除value为空的key的对象').deleteObjValueLengthIs0('boolean是否修改原对象 true/不修改 false/修改 默认修改')
     * @param {*} bool:boolean
     * 功能:将对象中value为空的key删除掉
     * 返回值:删除value为空的对象
     */
    deleteObjValueLengthIs0: function (bool = false) {
      if (this.dataType === "FakeArray" || this.dataType === "Object") {
        if (bool) {
          let obj = {};
          HappyDevelopment.deepCopy(this[0], obj);
          let that = HappyDevelopment(obj)[0];
          HappyDevelopment(obj).each(function (item, index) {
            if (HappyDevelopment(item).isString() && item.length == 0) {
              delete that[index]
            }
          });
          return that;
        } else {
          let that = this[0];
          this.each(function (item, index) {
            if (HappyDevelopment(item).isString() && item.length == 0) {
              delete that[index]
            }
          });
          return that;
        }
      } else {
        throw Error('本函数为删除对象中值为空的函数，请输入对象或者伪数组')
      }
    },

    /**
     * 调用实例:hd(obj).deleteObjValueLengthIs0AndCorrespondingArr('由obj中需要删除key组成的数组', true)
     * @param {*} delAttrArr:Array
     * @param {*} bool:boolean
     * 功能：删除传入对象中value==''的属性，并且根据传入的由需要删除key值组成的数组，删除对应的key值
     * 返回值:去除好key的对象
     */
    deleteObjValueLengthIs0AndCorrespondingArr: function (delAttrArr, bool = false) {
      if (this.dataType === "FakeArray" || this.dataType === "Object") {
        if (HappyDevelopment(delAttrArr).dataType === "Array") {
          if (bool) {
            let obj = {};
            HappyDevelopment.deepCopy(this[0], obj);
            let that = HappyDevelopment(obj)[0];
            HappyDevelopment(obj).each(function (item, index) {
              if (HappyDevelopment(item).isString() && item.length == 0) {
                delete that[index]
              };
              if (delAttrArr.length > 0) {
                HappyDevelopment(delAttrArr).each(function (itemSon, itemIndex) {
                  delete that[itemSon]
                })
              }
            });
            return that;
          } else {
            let that = this[0];
            this.each(function (item, index) {
              if (HappyDevelopment(item).isString() && item.length == 0) {
                delete that[index]
              };
              if (delAttrArr.length > 0) {
                HappyDevelopment(delAttrArr).each(function (itemSon, itemIndex) {
                  delete that[itemSon]
                })
              }
            });
            return that;
          }
        } else {
          throw Error('deleteObjValueLengthIs0AndCorrespondingArr()输入的第一个参数必须是数组')
        }
      } else {
        throw Error('本函数为删除对象中值为空的函数，请输入对象或者伪数组')
      }
    },

    /**
     * 调用实例：hd('需要设置值为空的对象').setEmptyToObjKey()
     * 功能:将对象中所有的value都置空
     * 返回值：boolean
     * 是否修改原对象：是
     * 注意点：这个方法非递归循环
     */
    setEmptyToObjKey: function () {
      if (this.dataType === "FakeArray" || this.dataType === "Object") {
        let that = this[0];
        this.each(function (item, index) {
          that[index] = ""
        });
        return that;
      } else {
        throw Error('本函数为设置对象或者伪数组的value==""的方法，请输入对象或者伪数组')
      }
    },
  })

  /**
   * 添加实例方法-时间
   * 月份和日期的前面加0
   * 格式化时间
   */
  HappyDevelopment.prototype.extend({//添加实例方法-时间
    /**
     * 功能:在月份和日期的前面加0
     * 输入:2019-8-8
     * 返回值:2019-08-08
     */
    add0: function () {
      let str = this[0];
      let arr = str.split('-');
      arr.forEach((item, index) => {
        if (Number(item) < 10) {
          arr[index] = '0' + item
        }
      });
      return arr.join('-')
    },

    /**
     * 功能:格式化时间
     * 输入:2019/8/8
     * 返回值:2019-08-08
     */
    formatTime: function () {
      let str = this[0];
      let reg = /\//g;
      return HappyDevelopment(new Date(str).toLocaleDateString().replace(reg, '-')).add0();
    }
  })

  /**
   * 添加实例方法-数组操作
   * 数组去重
   * 向数组中添加对象
   */
  HappyDevelopment.prototype.extend({//添加实例方法-数组
    arrayWeightRemoval: function () {
      return [...new Set(this[0])]
    },

    /**
     * 功能:向数组中添加对象
     *    1.如果数组中没有该对象 ==> 添加
     *    2.如果数组中有该对象 ==> 更新
     * 是否修改原数组:是
     * 参数:
     *    targetArr:需要添加obj的数组
     *    selectObj:被添加到targetArr的对象
     *    compareAttr:用于对比的属性
     * 返回值:array
     */
    arrayAddObj: function (selectObj, compareAttr) {
      let bool = this[0].some(item => { //some循环可以自动终止
        if (item[compareAttr] == selectObj[compareAttr]) {
          Object.assign(item, selectObj);
          return true
        }
      });
      !bool && this[0].push(selectObj);
      return this[0];
    }
  });

  HappyDevelopment.prototype.extend({

  })

  HappyDevelopment.prototype.init.prototype = HappyDevelopment.prototype;
  window.hd = window.HappyDevelopment = HappyDevelopment;
})(window);